#!/usr/local/bin/python
# Rob Dennis - Lurchington Q. Delacroix
# Keasby - TV renamer
# Project Homepage: http://code.google.com/p/keasby/

#---Python Import
import logging
from base_classes import KeasbyTestCase

class KeasbyInfoRepo():
    """
    Class used to interact with one or more information sources, primarily
    intended to return the first TvEpisode instance found from a pre-set
    list of sources
    """
    def __init__(self):
        """
        Currently will not initialize any sources on initialization
        """

        self.sources = []
##        self._initialize_sources()
##        self._initialize_priority()

    def _initialize_sources(self):
        from tvdb_api import TvDbApi,TvDbApiError

        self.sources = [
                        TvDbApi("D1EBA6781E2572BB")
                        ]

    def _initialize_priority(self):
        """
        Used to specify the priority of a source relative to a different
        source. A higher priority means that a given source will be searched
        first
        """
        pass


    def get_tvepisode(self,search_args):
        """
        Returns the TvEpisode instance(s) found given the search
        arguments. For any argument that is a list, it is assumed
        that each element is part of a grouped, but seperate episode
        """

        if not self.sources:
            self._initialize_sources()
            self._initialize_priority()

        logging.debug("attempting to find TvEpisodes with "+
                                "search arguments: %s",search_args)

        return [self._source_search(search_map)
                    for search_map in self._split_search(search_args)]

    def _source_search(self,search_map):
        """
        Intended to search an individual TvEpisode
        """

        for source in self.sources:
            logging.debug("-----")
            logging.debug("using source: %s",source)
            logging.debug("using search arguments: %s",search_map)
            logging.debug("-----")

            try:
                found_episode = source.get_tvepisode(search_map)
            except KeasbySourceError:
                logging.error("unknown error using source: %s",source)
                raise

            if found_episode:
                #Adding the unused search parameters back to the results
                found_episode += search_map
                return found_episode

        raise KeasbyKeasbyInfoRepoError("Unable to find a valid TvEpisode using: %s",
                                        search_args)


    def _split_search(self, search_args):
        """
        Intended to split the search argument map into that number of
        discrete search maps

        Currently: only splits based on episode numbers
        """

        if ("episode_number" in search_args and
            isinstance(search_args["episode_number"],(list,tuple))):
            logging.debug("splitting search based on episode_number")

            temp_list = []

            for episode_number in search_args["episode_number"]:

                temp_map = search_args.copy()
                temp_map["episode_number"] = episode_number

                temp_list.append(temp_map)

            return temp_list

        else:

            logging.debug("not splitting search_args")
            logging.debug("return [%s]", search_args)
            return [search_args]



class KeasbySource():
    """
    Base class for all sources used by KeasbyInfoRepo
    specifies public interfaces that need to be created for all
    subclasses
    """
    def __init__(self, shortname="KeasbySource",
                    description="Generic KeasbySource"):

        self.__dict__.update(dict(shortname=shortname,description=description))


    def __str__(self):
        """
        prints shortname, suitable for display
        """
        return str(self.shortname)

    def query_search(self, search_map):
        """
        Must be overridden to return a list of the possible choices
        the user must choose from. A list length of one implies that
        there was only one possible result given the search_map
        and we immediately invoke get_tvepisode
        """

        raise NotImplementedError("query_source not defined in %s"%
                                    self.__class__.__name__)

    def downselect(self, chosen_result, kwargs):
        """
        Passes in one of the results from query_search, along with the
        original arguments. This result is expect to be stored to inform
        future choices
        """

        raise NotImplementedError("downselect not defined in %s"%
                                    self.__class__.__name__)


    def prime_search(self, kwargs):
        """
        After choosing which result corresponds to the provided arguments,
        store this information into a working area in order to be searched
        """

        raise NotImplementedError("prime_search not defined in %s"%
                                    self.__class__.__name__)

    def get_search(self, search_map):
        """
        Must be overridden to return a TvEpisode instance of the
        result of the supplied search_map
        """

        raise NotImplementedError("get_search not defined in %s"%
                                    self.__class__.__name__)


class KeasbySourceError(Exception):
    """
    RepoSourceError
    """

    pass


class KeasbyInfoRepoError(Exception):
    """
    KeasbyInfoRepoError
    """

    pass


class KeasbySourceTestCase(KeasbyTestCase):
    """
    Tests the base KeasbySource class, intended to be subclassed by
    sources. The source derived from KeasbySource should use the
    class attribute s. All tests will assume the source is accessible
    through self.s
    """
    def __init__(self, *args, **kwargs):
        """
        Sets up the test set to us
        """

        self.search_map = dict(showname = u"Lost",
                                season_number = 1,
                                episode_number = 1,
                                title = u"Pilot (1)")

        KeasbyTestCase.__init__(self, *args, **kwargs)

    def setUp(self):
        """
        Intended to be subclassed. KeasbySource is essentially an
        abstract class
        """

        self.s = KeasbySource()

    def test_query_search(self):
        """
        Tests a source for the implementation of query_search
        """

        if not self.valid_test():
            return None

        possibles = self.s.query_search(self.search_map)

        self.assertLog(possibles, "invalid list of possibles: %s"%possibles)

        for possibility in possibles:
            self.assertLog(possibility, "invalid possibility", possibility)

    def test_prime_search(self):
        """
        Tests a source for the implemention of prime_search
        """

        if not self.valid_test():
            return None

        possibles = self.s.query_search(self.search_map)

        self.s.downselect(possibles[0], self.search_map)

        self.s.prime_search(self.search_map)

    def test_downselect(self):
        """
        Tests a source for the implementation of downselect
        """

        if not self.valid_test():
            return None

        possibles = self.s.query_search(self.search_map)

        self.s.downselect(possibles[0], self.search_map)

    def test_get_search(self):
        """
        Tests a source for the implementation of get_search
        """

        if not self.valid_test():
            return None

        possibles = self.s.query_search(self.search_map)

        self.s.downselect(possibles[0], self.search_map)
        self.s.prime_search(self.search_map)

        found_map = self.s.get_search(self.search_map)

        self.assertLog(found_map,
                        "invalid search result: %s"%found_map,
                        "%s found %s with search: %s"%(self.s,
                                                        found_map,
                                                        self.search_map))

        self.assertDictSubset(self.search_map, found_map)

    def valid_test(self):
        """
        Returns True if this is a valid test and we should continue. If False
        we should end to not "fail" an invalid test
        """

        if not issubclass(self.s.__class__, KeasbySource):
            logging.warning("%s Not a subclass KeasbySource",
                                self.s.__class__.__name__)
            return False

        if  self.s.__class__ is KeasbySource:
            logging.warning("Don't run the base KeasbySource in a unit test")
            return False

        return True

